/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2018 Blender Foundation.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): Lukas Stockner, Stefan Werner
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/nodes/composite/nodes/node_composite_cryptomatte.c
 *  \ingroup cmpnodes
 */

#include "node_composite_util.h"
#include "BLI_assert.h"
#include "BLI_dynstr.h"
#include "BLI_hash_mm3.h"
#include "BLI_utildefines.h"

/* this is taken from the cryptomatte specification 1.0 */

BLI_INLINE float hash_to_float(uint32_t hash)
{
	uint32_t mantissa = hash & ((1 << 23) - 1);
	uint32_t exponent = (hash >> 23) & ((1 << 8) - 1);
	exponent = MAX2(exponent, (uint32_t)1);
	exponent = MIN2(exponent, (uint32_t)254);
	exponent = exponent << 23;
	uint32_t sign = (hash >> 31);
	sign = sign << 31;
	uint32_t float_bits = sign | exponent | mantissa;
	float f;
	/* Bit casting relies on equal size for both types. */
	BLI_STATIC_ASSERT(sizeof(float) == sizeof(uint32_t), "float and uint32_t are not the same size")
	memcpy(&f, &float_bits, sizeof(float));
	return f;
}

static void cryptomatte_add(NodeCryptomatte *n, float f)
{
	/* Turn the number into a string. */
	char number[32];
	BLI_snprintf(number, sizeof(number), "<%.9g>", f);

	/* Search if we already have the number. */
	if (n->matte_id && strlen(n->matte_id) != 0) {
		size_t start = 0;
		const size_t end = strlen(n->matte_id);
		size_t token_len = 0;
		while (start < end) {
			/* Ignore leading whitespace. */
			while (start < end && n->matte_id[start] == ' ') {
				++start;
			}

			/* Find the next seprator. */
			char *token_end = strchr(n->matte_id + start, ',');
			if (token_end == NULL || token_end == n->matte_id + start) {
				token_end = n->matte_id + end;
			}
			/* Be aware that token_len still contains any trailing white space. */
			token_len = token_end - (n->matte_id + start);

			/* If this has a leading bracket, assume a raw floating point number and look for the closing bracket. */
			if (n->matte_id[start] == '<') {
				if (strncmp(n->matte_id + start, number, strlen(number)) == 0) {
					/* This number is already there, so continue. */
					return;
				}
			}
			else {
				/* Remove trailing white space */
				size_t name_len = token_len;
				while (n->matte_id[start + name_len] == ' ' && name_len > 0) {
					name_len--;
				}
				/* Calculate the hash of the token and compare. */
				uint32_t hash = BLI_hash_mm3((const unsigned char *)(n->matte_id + start), name_len, 0);
				if (f == hash_to_float(hash)) {
					return;
				}
			}
			start += token_len + 1;
		}
	}

	DynStr *new_matte = BLI_dynstr_new();
	if (!new_matte) {
		return;
	}

	if (n->matte_id) {
		BLI_dynstr_append(new_matte, n->matte_id);
		MEM_freeN(n->matte_id);
	}

	if (BLI_dynstr_get_len(new_matte) > 0) {
		BLI_dynstr_append(new_matte, ",");
	}
	BLI_dynstr_append(new_matte, number);
	n->matte_id = BLI_dynstr_get_cstring(new_matte);
	BLI_dynstr_free(new_matte);
}

static void cryptomatte_remove(NodeCryptomatte *n, float f)
{
	if (n->matte_id == NULL || strlen(n->matte_id) == 0) {
		/* Empty string, nothing to remove. */
		return;
	}

	/* This will be the new string without the removed key. */
	DynStr *new_matte = BLI_dynstr_new();
	if (!new_matte) {
		return;
	}

	/* Turn the number into a string. */
	static char number[32];
	BLI_snprintf(number, sizeof(number), "<%.9g>", f);

	/* Search if we already have the number. */
	size_t start = 0;
	const size_t end = strlen(n->matte_id);
	size_t token_len = 0;
	bool is_first = true;
	while (start < end) {
		bool skip = false;
		/* Ignore leading whitespace or commas. */
		while (start < end && ((n->matte_id[start] == ' ') || (n->matte_id[start] == ','))) {
			++start;
		}

		/* Find the next seprator. */
		char *token_end = strchr(n->matte_id + start + 1, ',');
		if (token_end == NULL || token_end == n->matte_id + start) {
			token_end = n->matte_id + end;
		}
		/* Be aware that token_len still contains any trailing white space. */
		token_len = token_end - (n->matte_id + start);

		if (token_len == 1) {
			skip = true;
		}
		/* If this has a leading bracket, assume a raw floating point number and look for the closing bracket. */
		else if (n->matte_id[start] == '<') {
			if (strncmp(n->matte_id + start, number, strlen(number)) == 0) {
				/* This number is already there, so skip it. */
				skip = true;
			}
		}
		else {
			/* Remove trailing white space */
			size_t name_len = token_len;
			while (n->matte_id[start + name_len] == ' ' && name_len > 0) {
				name_len--;
			}
			/* Calculate the hash of the token and compare. */
			uint32_t hash = BLI_hash_mm3((const unsigned char *)(n->matte_id + start), name_len, 0);
			if (f == hash_to_float(hash)) {
				skip = true;
			}
		}
		if (!skip) {
			if (is_first) {
				is_first = false;
			}
			else {
				BLI_dynstr_append(new_matte, ", ");
			}
			BLI_dynstr_nappend(new_matte, n->matte_id + start, token_len);
		}
		start += token_len + 1;
	}

	if (n->matte_id) {
		MEM_freeN(n->matte_id);
		n->matte_id = NULL;
	}
	if (BLI_dynstr_get_len(new_matte) > 0) {
		n->matte_id = BLI_dynstr_get_cstring(new_matte);
	}
	BLI_dynstr_free(new_matte);
}

static bNodeSocketTemplate outputs[] = {
	{   SOCK_RGBA,  0, N_("Image")},
	{   SOCK_FLOAT, 0, N_("Matte")},
	{   SOCK_RGBA,  0, N_("Pick")},
	{   -1, 0, ""   }
};

void ntreeCompositCryptomatteSyncFromAdd(bNodeTree *UNUSED(ntree), bNode *node)
{
	NodeCryptomatte *n = node->storage;
	if (n->add[0] != 0.0f) {
		cryptomatte_add(n, n->add[0]);
		n->add[0] = 0.0f;
		n->add[1] = 0.0f;
		n->add[2] = 0.0f;
	}
}

void ntreeCompositCryptomatteSyncFromRemove(bNodeTree *UNUSED(ntree), bNode *node)
{
	NodeCryptomatte *n = node->storage;
	if (n->remove[0] != 0.0f) {
		cryptomatte_remove(n, n->remove[0]);
		n->remove[0] = 0.0f;
		n->remove[1] = 0.0f;
		n->remove[2] = 0.0f;
	}
}

bNodeSocket *ntreeCompositCryptomatteAddSocket(bNodeTree *ntree, bNode *node)
{
	NodeCryptomatte *n = node->storage;
	char sockname[32];
	n->num_inputs++;
	BLI_snprintf(sockname, sizeof(sockname), "Crypto %.2d", n->num_inputs - 1);
	bNodeSocket *sock = nodeAddStaticSocket(ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, NULL, sockname);
	return sock;
}

int ntreeCompositCryptomatteRemoveSocket(bNodeTree *ntree, bNode *node)
{
	NodeCryptomatte *n = node->storage;
	if (n->num_inputs < 2) {
		return 0;
	}
	bNodeSocket *sock = node->inputs.last;
	nodeRemoveSocket(ntree, node, sock);
	n->num_inputs--;
	return 1;
}

static void init(bNodeTree *ntree, bNode *node)
{
	NodeCryptomatte *user = MEM_callocN(sizeof(NodeCryptomatte), "cryptomatte user");
	node->storage = user;


	nodeAddStaticSocket(ntree, node, SOCK_IN, SOCK_RGBA, PROP_NONE, "image", "Image");

	/* Add three inputs by default, as recommended by the Cryptomatte specification. */
	ntreeCompositCryptomatteAddSocket(ntree, node);
	ntreeCompositCryptomatteAddSocket(ntree, node);
	ntreeCompositCryptomatteAddSocket(ntree, node);
}

static void node_free_cryptomatte(bNode *node)
{
	NodeCryptomatte *nc = node->storage;

	if (nc) {
		if (nc->matte_id) {
			MEM_freeN(nc->matte_id);
		}

		MEM_freeN(nc);
	}
}

static void node_copy_cryptomatte(bNodeTree *UNUSED(dest_ntree), bNode *dest_node, bNode *src_node)
{
	NodeCryptomatte *src_nc = src_node->storage;
	NodeCryptomatte *dest_nc = MEM_dupallocN(src_nc);

	if (src_nc->matte_id)
		dest_nc->matte_id = MEM_dupallocN(src_nc->matte_id);

	dest_node->storage = dest_nc;
}

void register_node_type_cmp_cryptomatte(void)
{
	static bNodeType ntype;

	cmp_node_type_base(&ntype, CMP_NODE_CRYPTOMATTE, "Cryptomatte", NODE_CLASS_CONVERTOR, 0);
	node_type_socket_templates(&ntype, NULL, outputs);
	node_type_init(&ntype, init);
	node_type_storage(&ntype, "NodeCryptomatte", node_free_cryptomatte, node_copy_cryptomatte);
	nodeRegisterType(&ntype);
}
